/******************************************************************************
*
*  File Version Info v1.2 plugin for FAR Manager 1.70+/2.0+
*
*  Copyright (c) 2005 Creware (http://www.creware.com, support@creware.com)
*  Copyright (c) 2006 Alexander Ogorodnikov (anodyne@mail.ru)
*  Copyright (c) 2010 Andrey Voloshinov (xsilver@mail.ru)
*
*  Displays file version information stored in the VERSIONINFO resource
*
******************************************************************************/

#ifndef WIN32_LEAN_AND_MEAN
#  define WIN32_LEAN_AND_MEAN
#endif
#if defined(_MSC_VER)
#  define _CRT_SECURE_NO_WARNINGS
#endif

#include <Windows.h>
#include <tchar.h>

#if defined(_UNICODE)
#  include "farapi2/plugin.hpp"
#else
#  include "farapi1/plugin.hpp"
#endif

#include <stdlib.h>
#include <string.h>

//#pragma comment(lib, "Version.lib")

#define DLG_MAX_LINES	32

static struct PluginStartupInfo Info;
static LPCTSTR g_lpszDefOkButton = _T("Ok");
static LPCTSTR g_lpszDefTitle = _T("Version Info");

typedef struct TAG_DIALOG
{
	LPCTSTR		Title;
	LPCTSTR		OkButton;
	size_t		nLines;
	LPTSTR		Lines[DLG_MAX_LINES];
} 
DIALOG, *PDIALOG;

static PDIALOG DialogCreate(LPCTSTR Title)
{
	PDIALOG pDlg = (PDIALOG)malloc(sizeof(DIALOG));
	if (pDlg)
	{
		memset(pDlg, 0, sizeof(DIALOG));
		if ((Title == g_lpszDefTitle) || ((!Title) || (!*Title)))
			pDlg->Title = g_lpszDefTitle;
		else
		{
			pDlg->Title = _tcsdup(Title);
			if (!pDlg->Title)
				pDlg->Title = g_lpszDefTitle;
		}
		pDlg->OkButton = g_lpszDefOkButton;
	}
	return pDlg;
}

static void DialogFree(PDIALOG pDlg)
{
	if (pDlg)
	{
		size_t i;
		for (i = 0; i < pDlg->nLines; i++)
		{
			if (pDlg->Lines[i])
				free(pDlg->Lines[i]);
		}
		if (pDlg->Title && pDlg->Title != g_lpszDefTitle)
			free((void*)pDlg->Title);
		free(pDlg);
	}
}

static LPTSTR DialogAddLinePlaceholder(PDIALOG pDlg, size_t nChars)
{
	if (pDlg->nLines >= DLG_MAX_LINES)
		return NULL;
	LPTSTR p = (LPTSTR)malloc(sizeof(TCHAR) * nChars);
	if (NULL != p)
		pDlg->Lines[pDlg->nLines++] = p;
	return p;
}

static BOOL DialogAddLine(PDIALOG pDlg, LPCTSTR psz)
{
	if (pDlg->nLines >= DLG_MAX_LINES)
		return NULL;
	LPTSTR p = _tcsdup(psz);
	if (NULL == p)
		return FALSE;
	pDlg->Lines[pDlg->nLines++] = p;
	return TRUE;
}

static int DialogShow(PDIALOG pDlg)
{
	if (pDlg->nLines == 0) 
		return -1;

	LPCTSTR* Msg = (LPCTSTR*)malloc(sizeof(LPCTSTR) * (pDlg->nLines + 3));
	size_t	ndx = 0;
	size_t	i;
	int		nResult;

	if (Msg == NULL) 
		return -2;

	Msg[ndx++] = pDlg->Title;

	for (i = 0; i < pDlg->nLines; i++)
	{
		Msg[ndx++] = pDlg->Lines[i];
	}

	Msg[ndx++] = _T("\x01");
	Msg[ndx++] = pDlg->OkButton;

	//typedef int (WINAPI *FARAPIMESSAGE)(
	 // INT_PTR PluginNumber,
	 // DWORD Flags,
	 // const wchar_t *HelpTopic,
	 // const wchar_t * const *Items,
	 // int ItemsNumber,
	 // int ButtonsNumber
	 // );

	nResult = Info.Message(Info.ModuleNumber, FMSG_LEFTALIGN, _T("Contents"), Msg, (int)ndx, 1);
	free(Msg);
	return nResult;
}

static void MsgBox(LPCTSTR pszTitle, LPCTSTR pszMsg, BOOL bError = FALSE)
{
	LPCTSTR Msg[4];

	Msg[0] = pszTitle;
	Msg[1] = pszMsg;
	Msg[2] = _T("\x01");
	Msg[3] = g_lpszDefOkButton;

	Info.Message(Info.ModuleNumber, (bError ? FMSG_WARNING : 0) + FMSG_LEFTALIGN,
		_T("Contents"), Msg, 4, 1);
}

static size_t GetLanguageName(LCID Locale, LPTSTR lpLCData, size_t cchData)
{
    static TCHAR pszLangIndependent[] = _T("language independent");

    if (Locale == 0)
    {
		size_t len = _tcslen(pszLangIndependent);
		if (!cchData)
			return len + 1;
		if (len >= cchData)
			len = cchData - 1;
		_tcsncpy(lpLCData, pszLangIndependent, len);
		lpLCData[len] = 0;
        return len;
    }
    return (size_t)GetLocaleInfo(Locale, LOCALE_SLANGUAGE, lpLCData, (int)cchData);
}

BOOL ProcessFile(LPCTSTR szFilePath)
{
	BOOL    bShown   = FALSE;
	LPVOID  pData    = NULL;
	DWORD   dwHandle = 0;
	DWORD   dwLength = GetFileVersionInfoSize(szFilePath, &dwHandle);
	PDIALOG pDlg     = NULL;

	if (dwLength)
	{
		pDlg = DialogCreate(g_lpszDefTitle);
		if (!pDlg)
			return FALSE;

	    pData = malloc(dwLength);
		while (pData)
		{
			VS_FIXEDFILEINFO*	pFFI = NULL;
			UINT				uLen  = 0;
			BOOL				bResult;
			TCHAR				szTemp[128];
			TCHAR				szTemp2[128];
			DWORD				dwFlags;
			LPTSTR				pszString;

			bResult = GetFileVersionInfo(szFilePath, dwHandle, dwLength, pData);
			if (!bResult)
			{
				DialogAddLine(pDlg, _T("Cannot retrieve version information."));
				break;
			}
			
			bResult = VerQueryValue(pData, _T("\\"), (LPVOID*)&pFFI, &uLen);
			if (!bResult)
			{
				DialogAddLine(pDlg, _T("Cannot retrieve version information."));
				break;
			}

			wsprintf(szTemp, _T("File version      %u.%u.%u.%u"), 
				(unsigned int)HIWORD(pFFI->dwFileVersionMS),
				(unsigned int)LOWORD(pFFI->dwFileVersionMS),
				(unsigned int)HIWORD(pFFI->dwFileVersionLS),
				(unsigned int)LOWORD(pFFI->dwFileVersionLS) 
				);
			DialogAddLine(pDlg, szTemp);

			wsprintf(szTemp, _T("Product version   %u.%u.%u.%u"), 
				(unsigned int)HIWORD(pFFI->dwProductVersionMS),
				(unsigned int)LOWORD(pFFI->dwProductVersionMS),
				(unsigned int)HIWORD(pFFI->dwProductVersionLS),
				(unsigned int)LOWORD(pFFI->dwProductVersionLS) 
				);
			DialogAddLine(pDlg, szTemp);

			switch (pFFI->dwFileType)
			{
			case VFT_APP:        pszString = _T("application"); break;
			case VFT_DLL:        pszString = _T("dynamic-link library"); break;
			case VFT_DRV:        pszString = _T("device driver"); break;
			case VFT_FONT:       pszString = _T("font"); break;
			case VFT_VXD:        pszString = _T("virtual device"); break;
			case VFT_STATIC_LIB: pszString = _T("static-link library"); break;
			default:
				wsprintf(szTemp2, _T("unknown (%u)"), pFFI->dwFileType);
				pszString = szTemp2;
			}
			wsprintf(szTemp, _T("File type         %s"), pszString);
			DialogAddLine(pDlg, szTemp);

			if (pFFI->dwFileType == VFT_DRV)
			{
				switch(pFFI->dwFileSubtype)
				{
				case VFT2_DRV_PRINTER:           pszString = _T("printer driver"); break;
				case VFT2_DRV_KEYBOARD:          pszString = _T("keyboard driver"); break;
				case VFT2_DRV_LANGUAGE:          pszString = _T("language driver"); break;
				case VFT2_DRV_DISPLAY:           pszString = _T("display driver"); break;
				case VFT2_DRV_MOUSE:             pszString = _T("mouse driver"); break;
				case VFT2_DRV_NETWORK:           pszString = _T("network driver"); break;
				case VFT2_DRV_SYSTEM:            pszString = _T("system driver"); break;
				case VFT2_DRV_INSTALLABLE:       pszString = _T("installable driver"); break;
				case VFT2_DRV_SOUND:             pszString = _T("sound driver"); break;
				case VFT2_DRV_COMM:              pszString = _T("communications driver"); break;
				case VFT2_DRV_INPUTMETHOD:       pszString = _T("input method editor"); break;
				case VFT2_DRV_VERSIONED_PRINTER: pszString = _T("versioned printer driver"); break;
				default:
					wsprintf(szTemp2, _T("unknown (%u)"), pFFI->dwFileSubtype);
					pszString = szTemp2;
				}
				wsprintf(szTemp, _T("File subtype      %s"), pszString);
				DialogAddLine(pDlg, szTemp);
			}
			else if (pFFI->dwFileType == VFT_FONT)
			{
				switch(pFFI->dwFileSubtype)
				{
				case VFT2_FONT_RASTER:   pszString = _T("raster font"); break;
				case VFT2_FONT_VECTOR:   pszString = _T("vector font"); break;
				case VFT2_FONT_TRUETYPE: pszString = _T("TrueType font"); break;
				default:
					wsprintf(szTemp2, _T("unknown (%u)"), pFFI->dwFileSubtype);
					pszString = szTemp2;
				}
				wsprintf(szTemp, _T("File subtype      %s"), pszString);
				DialogAddLine(pDlg, szTemp);
			}
			else if (pFFI->dwFileType == VFT_VXD)
			{
				wsprintf(szTemp, _T("Device ID         0x%04X"), pFFI->dwFileSubtype);
				DialogAddLine(pDlg, szTemp);
			}

			dwFlags = pFFI->dwFileFlags & pFFI->dwFileFlagsMask;
			if (dwFlags != 0)
			{
				lstrcpy(szTemp, _T("File flags       "));
				if (dwFlags & VS_FF_DEBUG)        lstrcat(szTemp, _T(" DEBUG"));
				if (dwFlags & VS_FF_INFOINFERRED) lstrcat(szTemp, _T(" INFOINFERRED"));
				if (dwFlags & VS_FF_PATCHED)      lstrcat(szTemp, _T(" PATCHED"));
				if (dwFlags & VS_FF_PRERELEASE)   lstrcat(szTemp, _T(" PRERELEASE"));
				if (dwFlags & VS_FF_PRIVATEBUILD) lstrcat(szTemp, _T(" PRIVATEBUILD"));
				if (dwFlags & VS_FF_SPECIALBUILD) lstrcat(szTemp, _T(" SPECIALBUILD"));
				DialogAddLine(pDlg, szTemp);
			}

			switch(pFFI->dwFileOS)
			{
			case VOS_DOS:           pszString = _T("MS-DOS"); break;
			case VOS_OS216:         pszString = _T("OS/2 (16 bit)"); break;
			case VOS_OS232:         pszString = _T("OS/2 (32 bit)"); break;
			case VOS_NT:
			case VOS_NT_WINDOWS32:  pszString = _T("Windows NT/2000/XP/2003"); break;
			case VOS_WINCE:         pszString = _T("Windows CE"); break;
			case VOS__WINDOWS16:    pszString = _T("Windows (16 bit)"); break;
			case VOS__PM16:         pszString = _T("PM (16 bit)"); break;
			case VOS__PM32:         pszString = _T("PM (32 bit)"); break;
			case VOS__WINDOWS32:    pszString = _T("Windows (32 bit)"); break;
			case VOS_DOS_WINDOWS16: pszString = _T("Windows (16 bit) with MS-DOS"); break;
			case VOS_DOS_WINDOWS32: pszString = _T("Windows (32 bit) with MS-DOS"); break;
			case VOS_OS216_PM16:    pszString = _T("OS/2 with PM (16 bit)"); break;
			case VOS_OS232_PM32:    pszString = _T("OS/2 with PM (32 bit)"); break;
			default:
				wsprintf(szTemp2, _T("unknown (0x%.8X)"), pFFI->dwFileOS);
				pszString = szTemp2;
			}
			wsprintf(szTemp, _T("Operating system  %s"), pszString);
			DialogAddLine(pDlg, szTemp);

			if (pFFI->dwFileDateLS | pFFI->dwFileDateMS)
			{
				FILETIME   ft;
				FILETIME   ftLocal;

				ft.dwHighDateTime = pFFI->dwFileDateMS;
				ft.dwLowDateTime  = pFFI->dwFileDateLS;

				if (FileTimeToLocalFileTime(&ft, &ftLocal))
				{
					SYSTEMTIME stCreate;

					FileTimeToSystemTime(&ftLocal, &stCreate);

					wsprintf(szTemp, _T("File date         %02d/%02d/%d  %02d:%02d:%02d.%03d"),
						stCreate.wDay, stCreate.wMonth, stCreate.wYear,
						stCreate.wHour, stCreate.wMinute, stCreate.wSecond, 
						stCreate.wMilliseconds);

					DialogAddLine(pDlg, szTemp);
				}
				else
				{
					DialogAddLine(pDlg, _T("File date         invalid"));
				}
			}

		    // string information block

			static struct KEYVALUEPAIR 
			{
				LPCTSTR	pszKey;
				LPCTSTR	pszValue;
			} 
			KeyVal[] =
			{
				_T("FileVersion"),      _T("File version      "),
				_T("ProductVersion"),   _T("Product version   "),
				_T("FileDescription"),  _T("File description  "),
				_T("InternalName"),     _T("Internal name     "),
				_T("OriginalFilename"), _T("Original filename "),
				_T("PrivateBuild"),     _T("Private build     "),
				_T("SpecialBuild"),     _T("Special build     "),
				_T("ProductName"),      _T("Product name      "),
				_T("CompanyName"),      _T("Company name      "),
				_T("LegalCopyright"),   _T("Legal copyright   "),
				_T("LegalTrademarks"),  _T("Legal trademarks  "),
				_T("Comments"),         _T("Comments          "),
			};

			TCHAR pszLangCP[6][16] = 
			{
				_T("040904E4"), // English (US) and multilingual character set
				_T("040904B0"), // English (US) and Unicode character set
				_T("FFFFFFFF"), // system default language and current ANSI code page
				_T("FFFF04B0"), // system default language and Unicode character set
				_T("000004E4"), // no language and multilingual character set
				_T("000004B0")  // no language and Unicode character set
			};
			DWORD dwLangID = GetSystemDefaultLangID() << 16;

			wsprintf(pszLangCP[2], _T("%.8X"), dwLangID | GetACP());
			wsprintf(pszLangCP[3], _T("%.8X"), dwLangID | 0x04B0);	// CP_UNICODE

			// the pszStringName buffer must fit at least this string:
			// \StringFileInfo\12345678\OriginalFilename + 22 chars just in case
			TCHAR pszStringName[1+14+1+8+1+16+1+22] = _T("\\StringFileInfo\\");
		    BOOL bStringTableFound = FALSE;
			size_t i;

			for(i = 0; i < _countof(pszLangCP); i++)
			{
				lstrcpy(&pszStringName[16], pszLangCP[i]);
				bStringTableFound = VerQueryValue(pData, pszStringName, (LPVOID *) &pszString, &uLen);
				if (bStringTableFound) 
					break;
			}

		    if (bStringTableFound)
			{
				DialogAddLine(pDlg, _T("\x01"));
		        pszStringName[16 + 8] = _T('\\');

				for (i = 0; i < _countof(KeyVal); i++)
				{
					_tcscpy(&pszStringName[16 + 9], KeyVal[i].pszKey);
					if (VerQueryValue(pData, pszStringName, (LPVOID *) &pszString, &uLen))
					{
						LPTSTR pszSrc = pszString - 1;
						if (!uLen) uLen++;					// null place
						while (*++pszSrc)
						{
							// '\xA9' - copyright, '\xAE' - registered trademark
							if (*pszSrc == _T('\xA9') || *pszSrc == _T('\xAE')) 
								uLen += 2;
						}

						size_t cchValue = _tcslen(KeyVal[i].pszValue);
						LPTSTR pszLine = DialogAddLinePlaceholder(pDlg, cchValue + uLen);
						LPTSTR pszDest = pszLine;

						_tcscpy(pszDest, KeyVal[i].pszValue);
						pszDest += cchValue;

						pszSrc = pszString - 1;
						while (*++pszSrc)
						{
							if(*pszSrc == _T('\xA9'))
							{
								*pszDest++ = _T('('); 
								*pszDest++ = _T('C'); 
								*pszDest++ = _T(')');
							}
							else if (*pszSrc == _T('\xAE'))
							{
								*pszDest++ = _T('('); 
								*pszDest++ = _T('R'); 
								*pszDest++ = _T(')');
							}
							else *pszDest++ = *pszSrc;
						}
						*pszDest = 0;
#if !defined(_UNICODE)
						CharToOem(pszLine, pszLine);
#endif
					}
				}
			}

		    // variable information block

			struct LANGANDCODEPAGE 
			{
				WORD wLanguage;
				WORD wCodePage;
			} 
			*pLangAndCodePage;

			if (VerQueryValue(pData, _T("\\VarFileInfo\\Translation"), (LPVOID *)&pLangAndCodePage, &uLen))
		    {
			    static TCHAR pszPrompt[] = _T("Language          ");
		        size_t cchLang = 0, i, cLang = uLen / sizeof(LANGANDCODEPAGE);

				for (i = 0; i < cLang; i++)
					cchLang += GetLanguageName(pLangAndCodePage[i].wLanguage, NULL, 0);

				size_t	cchPrompt = _tcslen(pszPrompt);
				LPTSTR	pszLang = DialogAddLinePlaceholder(pDlg, cchPrompt + cchLang + cLang);
		        LPTSTR	pszDest = pszLang;

				_tcscpy(pszDest, pszPrompt);
				pszDest += cchPrompt;

		        for (i = 0;; i++)
				{
					pszDest += GetLanguageName(pLangAndCodePage[i].wLanguage, pszDest, cchLang);
					if (i == cLang - 1) 
						break;
					pszDest[-1] = _T(','); 
					*pszDest++ = _T(' ');
				}

#if !defined(_UNICODE)
				CharToOem(pszLang, pszLang);
#endif
			}
			break;
		}

		if (pDlg->nLines > 0)
			bShown = DialogShow(pDlg) == 0;

		if (pData)
			free(pData);
		DialogFree(pDlg);
	}
	else
	{
		MsgBox(g_lpszDefTitle, _T("No version information available"));
	}
	
	return bShown;
}

BOOL DoIt()
{
#if ((FARMANAGERVERSION_MAJOR-0) < 2)
	struct PanelInfo PInfo;
	Info.Control(INVALID_HANDLE_VALUE, FCTL_GETPANELINFO, &PInfo);

	if (PInfo.PanelType != PTYPE_FILEPANEL)
		return TRUE;

	if (PInfo.ItemsNumber && (PInfo.CurrentItem >= 0))
	{
		int nLen = lstrlen(PInfo.CurDir);

    if (nLen)
    {
      char  szFilePath[MAX_PATH];
      char* pszFileName;

      lstrcpyA(szFilePath, PInfo.CurDir);
      if (szFilePath[nLen-1] != '\\')
      {
        lstrcatA(szFilePath, "\\");
      }

      pszFileName = PInfo.PanelItems[PInfo.CurrentItem].FindData.cFileName;

      lstrcatA(szFilePath, pszFileName);

      return ProcessFile(szFilePath);
    }
  }
	return TRUE;
#else
	PanelInfo PInfo;
	BOOL bResult;

	ZeroMemory(&PInfo, sizeof(PInfo));
	bResult = Info.Control(PANEL_ACTIVE, FCTL_GETPANELINFO, 0, (LONG_PTR)&PInfo);

	if (!bResult)
		return TRUE;
	
	if (PInfo.PanelType != PTYPE_FILEPANEL)
		return TRUE;

	PluginPanelItem* ppItem = NULL;
	bResult = FALSE;
	while (PInfo.ItemsNumber && (PInfo.CurrentItem >= 0))
	{
		TCHAR szPath[MAX_PATH * 2];
		size_t			cbItem;
		int ccPath;
		int ccName;
		LPTSTR p;

		cbItem = (size_t)Info.Control(
			PANEL_ACTIVE, 
			FCTL_GETPANELITEM, 
			PInfo.CurrentItem, 
			NULL);
		if (0 == cbItem)
			break;
		ppItem = (PluginPanelItem*)malloc(cbItem);
		if (!ppItem)
			break;
		Info.Control(PANEL_ACTIVE, FCTL_GETPANELITEM, PInfo.CurrentItem, (LONG_PTR)ppItem);
		ccName = (ppItem->FindData.lpwszFileName) ? lstrlen(ppItem->FindData.lpwszFileName) : 0;
		if (!ccName)
			break;
		szPath[0] = 0;
		Info.Control(PANEL_ACTIVE, FCTL_GETPANELDIR, _countof(szPath), (LONG_PTR)szPath);
		ccPath = lstrlen(szPath);
		if (ccPath == 0)
			break;
		if ((ccPath + ccName + 1) > _countof(szPath))
			break;
		p = &szPath[ccPath - 1];
		if (*p++ != _T('\\'))
			*p++ = _T('\\');
		lstrcpy(p, ppItem->FindData.lpwszFileName);
		
		bResult = ProcessFile(szPath);
		break;
	}
	
	if (ppItem)
		free(ppItem);

	return bResult;
#endif

}

#if ((FARMANAGERVERSION_MAJOR-0) >= 2)
#  define SetStartupInfo	SetStartupInfoW
#  define GetPluginInfo		GetPluginInfoW
#  define OpenPlugin		OpenPluginW
#endif

void WINAPI _export SetStartupInfo(const struct PluginStartupInfo *psi)
{
	Info = *psi;
}

void WINAPI _export GetPluginInfo(struct PluginInfo *pi)
{
	static LPCTSTR PluginMenuStrings[1];

	pi->StructSize = sizeof(struct PluginInfo);
	//pi->Flags      = PF_EDITOR;

	PluginMenuStrings[0] = g_lpszDefTitle;

	pi->PluginMenuStrings = PluginMenuStrings;
	pi->PluginMenuStringsNumber = _countof(PluginMenuStrings);
}

HANDLE WINAPI _export OpenPlugin(int /*OpenFrom*/, int /*item*/)
{
	DoIt();
	return  INVALID_HANDLE_VALUE;
}

extern "C"
BOOL
APIENTRY 
DllMain( 
	HANDLE	hModule, 
    DWORD	ul_reason_for_call, 
    LPVOID	lpReserved
	)
{
	(lpReserved);
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		DisableThreadLibraryCalls((HINSTANCE)hModule);
		break;
	}
	return TRUE;
}

